%% class_defs.erl
%%   Functions for collecting class definitions from modules and assembling them.
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(class_defs).
-include("common_defs.hrl").
-export([scan/2, assemble_ldef/1, append_ldef/2]).

%% Function: process_fieldmap(Fields, Pos)
%% Purpose: sets global position in all the fields in a list
%% Arguments:
%%   Fields: [#class_field{}] a list of fields
%%   Pos: value to set each #class_field.global_pos
%% Returns: the updated version of Fields
process_fieldmap([Field|Fs], Pos) ->
    [Field#class_field{global_pos = Pos+?FIELD_GLOBAL_STARTPOS-1}
    |process_fieldmap(Fs, Pos)];
process_fieldmap([], _) ->
    [].

%% Function: add_to_vmt(VMT, Method, ClassLocal)
%% Purpose: adds a method declared in this class into the vmt: overrides the same Name/Arity
%%          functions from superclasses, or adds a new entry.
%% Arguments:
%%  VMT: [{Name, Arity, ImplementorClass, ImplementorClassImplModule}]
%%  Method = {Name, FArity}:  method to add
%%  ClassLocal: local definition of current class (class name, and implementation module name is 
%%              used to create new vmt entry)
%% Returns:
%%     VMT with a new entry for Method added, or if an entry for Method already existed,
%%     then the result is VMT with that entry updated.
add_to_vmt([], {Name, Arity}, #class_local{name = ThisClass, impl_module = ImplModule}) ->
    [{Name, Arity, ThisClass, ImplModule}];
add_to_vmt([{Name, Arity, _, _}|VMT], {Name, Arity}, #class_local{name = ThisClass, impl_module = ImplModule}) ->
    [{Name, Arity, ThisClass, ImplModule}|VMT];
add_to_vmt([Entry1 = {Name1, Arity1, _, _}|VMT], {Name2, Arity2}, LocalClassDef) when (Name1 /= Name2) or (Arity1 /= Arity2) ->
    [Entry1|add_to_vmt(VMT, {Name2, Arity2}, LocalClassDef)].

%% Function: add_to_vmt(Methods, VMT, ClassLocal)
%% Purpose: adds a list of methods declared in this class into the vmt: overrides the same Name/Arity
%%          functions from superclasses, or adds a new entries.
%% Arguments:
%%  Methods = [{Name, FArity}]:  list of methods to add
%%  VMT: [{Name, Arity, ImplementorClass, ImplementorClassImplModule}]
%%  ClassLocal: local definition of current class (class name, and implementation module name is 
%%              used to create new vmt entry)
%% Returns:
%%     VMT with its original entries and updated/added entries for each method in Methods
update_vmt([], VMT, _) ->
    VMT;
update_vmt([M|Methods], VMT, LocalClassDef) ->
    update_vmt(Methods, add_to_vmt(VMT, M, LocalClassDef), LocalClassDef).

update_vmt(LocalClassDef, SuperVMT) ->
    update_vmt(LocalClassDef#class_local.methods, SuperVMT, LocalClassDef).

%----

%% Function: assemble_ldef(LDefs)
%% Purpose: acquires the full definition of a class, given the local definitions of all its direct/indirect superclasses
%%          and the local definition of the class itself.
%% Args:
%%   LDefs: a list of local class definitions [#class_local{}]. The last item has no superclasses, the superclass
%%          of each other class is the class item in the list.
%% Returns: The full class definition of the class represented by the last local class definition in the list.
assemble_ldef(LDefs0) ->
    LDefs1 = lists:reverse(LDefs0),
    [#class_local{
        name = Class,
        impl_module = Impl,
        super_class = none,
        fields = Fields,
        version = LocalVersion,
        functions = Functions} = LocalClassDef
    |LDefs] = LDefs1,

    ClassDef = 
        #class{
            name = Class,
            class_types = {{Class, Impl}},
            fieldmap = process_fieldmap(Fields, 1),
            local_version = LocalVersion,
            global_version = LocalVersion,
            vmt = update_vmt(LocalClassDef, []),
            functions = Functions,
            src = [LocalClassDef]
        },
    append_ldef(LDefs, ClassDef).

%% Function: append_ldef(LDefs, SuperClassDef)
%% Purpose: acquires the full definition of a class, given the full definition of a (possibly indirect) superclass of it
%%          and local definitions of classes between the superclass and the class
%% Args:
%%   Class: list of local class definitions. [#class_local{}] The first one is the subclass of SuperClassDef, and the
%%          each subsequent one is a subclass of the previous.
%%   SuperClassDef: a full class definition. #class{}
%% Returns: The full class definition of the class represented by the last item of LDefs. To calculate this,
%%          the previous items of LDefs and SuperClassDef is used.
append_ldef(
        [#class_local{
            name = Class,
            impl_module= Impl,
            version = Version,
            super_class = {ok, SuperClass},
            fields = Fields,
            functions = Functions} = LocalClassDef
        |LDefs],
        SuperClassDef) ->
    #class{
        name = SuperClass,
        class_types = SuperTypes,
        fieldmap = SuperFields,
        global_version = SuperVersion,
        vmt = SuperVMT,
        src = SuperSrc
    } = SuperClassDef,
    ClassDef =
        #class{
            name = Class,
            class_types = erlang:append_element(SuperTypes, {Class, Impl}),
            fieldmap = SuperFields++process_fieldmap(Fields, tuple_size(SuperTypes)+1),
            local_version = Version,
            global_version = SuperVersion+Version-1,
            vmt = update_vmt(LocalClassDef, SuperVMT),
            functions = Functions,
            src = SuperSrc++[LocalClassDef]
        },
    append_ldef(LDefs, ClassDef);
append_ldef([], ClassDef1 = #class{class_types = ClassTypes}) ->
    SuperClass =
        case tuple_size(ClassTypes) of
        1 ->
            none;
        S ->
            {ok, element(1, element(S-1, ClassTypes))}
        end,
    ClassLevel = tuple_size(ClassTypes),
    ClassDef2 =
        ClassDef1#class{
            impl_module = element(2, element(ClassLevel, ClassTypes)),
            super_class = SuperClass,
            class_level = ClassLevel
        },
    ClassDef2.

%% Function: collect_ldef(Class, DefCache)
%% Purpose: collects all the local definitions of a class, using beam files on the disk and DefCache as the source
%% Args:
%%   Class: the class whose definition is seeked
%%   DefCache: (accumulator): a classname -> [#class_local{}] map for all the classes found so far
%%             the list contains all the local definitions of the class
%% Returns: {Definition, DefCache2}
%%          where Definition is the list of local definitions of the class, in the form of [#class_local{}]
%%          and DefCache2 is DefCache with the entries added for all the newly discovered classes
collect_ldef(Class, Defs1) ->
    case gb_trees:lookup(Class, Defs1) of
    {value, Def} ->
        {Def, Defs1};
    none ->
        {LocalClassData, _} = modules:get_local_classdata(Class, proxy, disk),
        
        #class_local{name = Class, super_class = SC} = LocalClassData,
        % put definition of superclass into classdata cache
        {Def, Defs2} =
            case SC of
            none ->
                {[LocalClassData], Defs1};
            {ok, SuperClass} ->
                {SuperClassLDef, Defs02} = collect_ldef(SuperClass, Defs1),
                {[LocalClassData|SuperClassLDef], Defs02}
            end,
        {Def, gb_trees:insert(Class, Def, Defs2)}
    end.

%% Function: expand_import(Class, DefCache)
%% Purpose: builds the full class definitions for a class
%%          For superclass definition information, DefCache, and beam files on the disk are used.
%% Args:
%%   Class: the class whose definition is seeked
%%   DefCache: (accumulator): a classname -> [#class_local{}] map for all the classes found so far
%%             the list contains all the local definitions of the class
%% Returns: {Definition, DefCache2}
%%          where Definition is the full Definition of Class, and has the form of #class{}
%%          and DefCache2 is DefCache with the entries added for all the newly discovered classes
expand_import(Class, DefCache1) ->
    {LDef, DefCache2} =
        case gb_trees:lookup(Class, DefCache1) of
        {value, Def} ->
            {Def, DefCache1};
        none ->
            collect_ldef(Class, DefCache1)
        end,
    {assemble_ldef(LDef), DefCache2}.

%% Function: expand_imports(Imports, DefList, DefCache)
%% Purpose: builds the full class definitions for each imported class (-import_class(Class) directive)
%%          For superclass definition information, DefCache, and beam files on the disk are used.
%% Args:
%%   Imports: list of imported classes, whose definitions are seeked
%%   DefList: (accumulator): list of all full definitions built so far, form of [#class{}]
%%   DefCache: (accumulator): a classname -> [#class_local{}] map for all the classes found so far
%%             the list contains all the local definitions of the class
%% Returns: {DefList2, DefCache2}
%%          where DefList2 is DefList with the full definitions of the classes in Imports added
%%          and DefCache2 is DefCache with the entries added for all the newly discovered classes
expand_imports([Class|Imports], DefList, DefCache1) ->
    {Def1, DefCache2} = expand_import(Class, DefCache1),
    %Def2 = process_fieldmap(Def1),
    Def2 = Def1,
    expand_imports(Imports, [Def2|DefList], DefCache2);
expand_imports([], DefList, DefCache) ->
    {DefList, DefCache}.

%% Function: scan(Forms, ThisClassData)
%% Purpose: collect all the class definitions imported to a module (may access beam files of other classes)
%% Args:
%%   Forms: abstract syntax tree of the module, whose imported classes are collected
%%   ThisClassData: {ok, Class = #class_local{}}, if the module defines a class
%%                  none if the module does not define a class
%% Returns: list of all the imported class definitions in the form of [#class{}]
%%          So the definitions of imported classes are assembled from local class definitions to
%%          full class definitions.
scan(Forms1, ThisClassData) ->
    Imports = misc:filtermap_list(fun ({attribute, _, import_class, ClassName}) -> {ok, ClassName};
                                      (_) -> false
                                  end, Forms1),

    {DefList1, DefCache1} = expand_imports(Imports, [], gb_trees:empty()),
    % add the definition of current class to the definition list:
    case ThisClassData of
    {ok, ThisClassDef = #class_local{super_class = SC}} ->
        LDef =
            case SC of
            none -> [ThisClassDef];
            {ok, SuperClass} -> 
                [ThisClassDef
                |
                    case gb_trees:lookup(SuperClass, DefCache1) of
                    {value, Val} -> Val;
                    none ->
                        throw(
                            {ect_compile_error,
                                {error_type1
                                ,io_lib:format("[ect] undefined class ~w for superclass (use -import_def(~w).)", [SuperClass, SuperClass])
                                }
                            })
                    end
                ]
            end,
        [assemble_ldef(LDef)
        |DefList1
        ];
    none ->
        DefList1
    end.